{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "f9656dc2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gym\n",
    "from collections import namedtuple\n",
    "import itertools\n",
    "from itertools import count\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "from torch.distributions.normal import Normal\n",
    "import numpy as np\n",
    "import collections\n",
    "import random\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8f060a63",
   "metadata": {},
   "outputs": [],
   "source": [
    "class PolicyNet(torch.nn.Module):\n",
    "    def __init__(self, state_dim, hidden_dim, action_dim, action_bound):\n",
    "        super(PolicyNet, self).__init__()\n",
    "        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)\n",
    "        self.fc_mu = torch.nn.Linear(hidden_dim, action_dim)\n",
    "        self.fc_std = torch.nn.Linear(hidden_dim, action_dim)\n",
    "        self.action_bound = action_bound\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.fc1(x))\n",
    "        mu = self.fc_mu(x)\n",
    "        std = F.softplus(self.fc_std(x))\n",
    "        dist = Normal(mu, std)\n",
    "        normal_sample = dist.rsample()  # rsample()是重参数化采样函数\n",
    "        log_prob = dist.log_prob(normal_sample)\n",
    "        action = torch.tanh(normal_sample)  # 计算tanh_normal分布的对数概率密度\n",
    "        log_prob = log_prob - torch.log(1 - torch.tanh(action).pow(2) + 1e-7)\n",
    "        action = action * self.action_bound\n",
    "        return action, log_prob\n",
    "\n",
    "\n",
    "class QValueNet(torch.nn.Module):\n",
    "    def __init__(self, state_dim, hidden_dim, action_dim):\n",
    "        super(QValueNet, self).__init__()\n",
    "        self.fc1 = torch.nn.Linear(state_dim + action_dim, hidden_dim)\n",
    "        self.fc2 = torch.nn.Linear(hidden_dim, 1)\n",
    "\n",
    "    def forward(self, x, a):\n",
    "        cat = torch.cat([x, a], dim=1)  # 拼接状态和动作\n",
    "        x = F.relu(self.fc1(cat))\n",
    "        return self.fc2(x)\n",
    "\n",
    "\n",
    "device = 'cpu'\n",
    "\n",
    "\n",
    "class SAC:\n",
    "    ''' 处理连续动作的SAC算法 '''\n",
    "    def __init__(self, state_dim, hidden_dim, action_dim, action_bound,\n",
    "                 actor_lr, critic_lr, alpha_lr, target_entropy, tau, gamma):\n",
    "        self.actor = PolicyNet(state_dim, hidden_dim, action_dim,\n",
    "                               action_bound).to(device)  # 策略网络\n",
    "        # 第一个Q网络\n",
    "        self.critic_1 = QValueNet(state_dim, hidden_dim, action_dim).to(device)\n",
    "        # 第二个Q网络\n",
    "        self.critic_2 = QValueNet(state_dim, hidden_dim, action_dim).to(device)\n",
    "        self.target_critic_1 = QValueNet(state_dim, hidden_dim,\n",
    "                                         action_dim).to(device)  # 第一个目标Q网络\n",
    "        self.target_critic_2 = QValueNet(state_dim, hidden_dim,\n",
    "                                         action_dim).to(device)  # 第二个目标Q网络\n",
    "        # 令目标Q网络的初始参数和Q网络一样\n",
    "        self.target_critic_1.load_state_dict(self.critic_1.state_dict())\n",
    "        self.target_critic_2.load_state_dict(self.critic_2.state_dict())\n",
    "        self.actor_optimizer = torch.optim.Adam(self.actor.parameters(),\n",
    "                                                lr=actor_lr)\n",
    "        self.critic_1_optimizer = torch.optim.Adam(self.critic_1.parameters(),\n",
    "                                                   lr=critic_lr)\n",
    "        self.critic_2_optimizer = torch.optim.Adam(self.critic_2.parameters(),\n",
    "                                                   lr=critic_lr)\n",
    "        # 使用alpha的log值,可以使训练结果比较稳定\n",
    "        self.log_alpha = torch.tensor(np.log(0.01), dtype=torch.float)\n",
    "        self.log_alpha.requires_grad = True  # 可以对alpha求梯度\n",
    "        self.log_alpha_optimizer = torch.optim.Adam([self.log_alpha],\n",
    "                                                    lr=alpha_lr)\n",
    "        self.target_entropy = target_entropy  # 目标熵的大小\n",
    "        self.gamma = gamma\n",
    "        self.tau = tau\n",
    "\n",
    "    def take_action(self, state):\n",
    "        state = torch.tensor([state], dtype=torch.float).to(device)\n",
    "        action = self.actor(state)[0]\n",
    "        return [action.item()]\n",
    "\n",
    "    def calc_target(self, rewards, next_states, dones):  # 计算目标Q值\n",
    "        next_actions, log_prob = self.actor(next_states)\n",
    "        entropy = -log_prob\n",
    "        q1_value = self.target_critic_1(next_states, next_actions)\n",
    "        q2_value = self.target_critic_2(next_states, next_actions)\n",
    "        next_value = torch.min(q1_value,\n",
    "                               q2_value) + self.log_alpha.exp() * entropy\n",
    "        td_target = rewards + self.gamma * next_value * (1 - dones)\n",
    "        return td_target\n",
    "\n",
    "    def soft_update(self, net, target_net):\n",
    "        for param_target, param in zip(target_net.parameters(),\n",
    "                                       net.parameters()):\n",
    "            param_target.data.copy_(param_target.data * (1.0 - self.tau) +\n",
    "                                    param.data * self.tau)\n",
    "\n",
    "    def update(self, transition_dict):\n",
    "        states = torch.tensor(transition_dict['states'],\n",
    "                              dtype=torch.float).to(device)\n",
    "        actions = torch.tensor(transition_dict['actions'],\n",
    "                               dtype=torch.float).view(-1, 1).to(device)\n",
    "        rewards = torch.tensor(transition_dict['rewards'],\n",
    "                               dtype=torch.float).view(-1, 1).to(device)\n",
    "        next_states = torch.tensor(transition_dict['next_states'],\n",
    "                                   dtype=torch.float).to(device)\n",
    "        dones = torch.tensor(transition_dict['dones'],\n",
    "                             dtype=torch.float).view(-1, 1).to(device)\n",
    "        rewards = (rewards + 8.0) / 8.0  # 对倒立摆环境的奖励进行重塑\n",
    "\n",
    "        # 更新两个Q网络\n",
    "        td_target = self.calc_target(rewards, next_states, dones)\n",
    "        critic_1_loss = torch.mean(\n",
    "            F.mse_loss(self.critic_1(states, actions), td_target.detach()))\n",
    "        critic_2_loss = torch.mean(\n",
    "            F.mse_loss(self.critic_2(states, actions), td_target.detach()))\n",
    "        self.critic_1_optimizer.zero_grad()\n",
    "        critic_1_loss.backward()\n",
    "        self.critic_1_optimizer.step()\n",
    "        self.critic_2_optimizer.zero_grad()\n",
    "        critic_2_loss.backward()\n",
    "        self.critic_2_optimizer.step()\n",
    "\n",
    "        # 更新策略网络\n",
    "        new_actions, log_prob = self.actor(states)\n",
    "        entropy = -log_prob\n",
    "        q1_value = self.critic_1(states, new_actions)\n",
    "        q2_value = self.critic_2(states, new_actions)\n",
    "        actor_loss = torch.mean(-self.log_alpha.exp() * entropy -\n",
    "                                torch.min(q1_value, q2_value))\n",
    "        self.actor_optimizer.zero_grad()\n",
    "        actor_loss.backward()\n",
    "        self.actor_optimizer.step()\n",
    "\n",
    "        # 更新alpha值\n",
    "        alpha_loss = torch.mean(\n",
    "            (entropy - self.target_entropy).detach() * self.log_alpha.exp())\n",
    "        self.log_alpha_optimizer.zero_grad()\n",
    "        alpha_loss.backward()\n",
    "        self.log_alpha_optimizer.step()\n",
    "\n",
    "        self.soft_update(self.critic_1, self.target_critic_1)\n",
    "        self.soft_update(self.critic_2, self.target_critic_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "bc8a17d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Swish(nn.Module):\n",
    "    ''' Swish激活函数 '''\n",
    "    def __init__(self):\n",
    "        super(Swish, self).__init__()\n",
    "\n",
    "    def forward(self, x):\n",
    "        return x * torch.sigmoid(x)\n",
    "\n",
    "\n",
    "def init_weights(m):\n",
    "    ''' 初始化模型权重 '''\n",
    "    def truncated_normal_init(t, mean=0.0, std=0.01):\n",
    "        torch.nn.init.normal_(t, mean=mean, std=std)\n",
    "        while True:\n",
    "            cond = (t < mean - 2 * std) | (t > mean + 2 * std)\n",
    "            if not torch.sum(cond):\n",
    "                break\n",
    "            t = torch.where(\n",
    "                cond,\n",
    "                torch.nn.init.normal_(torch.ones(t.shape, device=device),\n",
    "                                      mean=mean,\n",
    "                                      std=std), t)\n",
    "        return t\n",
    "\n",
    "    if type(m) == nn.Linear or isinstance(m, FCLayer):\n",
    "        truncated_normal_init(m.weight, std=1 / (2 * np.sqrt(m._input_dim)))\n",
    "        m.bias.data.fill_(0.0)\n",
    "\n",
    "\n",
    "class FCLayer(nn.Module):\n",
    "    ''' 集成之后的全连接层 '''\n",
    "    def __init__(self, input_dim, output_dim, ensemble_size, activation):\n",
    "        super(FCLayer, self).__init__()\n",
    "        self._input_dim, self._output_dim = input_dim, output_dim\n",
    "        self.weight = nn.Parameter(\n",
    "            torch.Tensor(ensemble_size, input_dim, output_dim).to(device))\n",
    "        self._activation = activation\n",
    "        self.bias = nn.Parameter(\n",
    "            torch.Tensor(ensemble_size, output_dim).to(device))\n",
    "\n",
    "    def forward(self, x):\n",
    "        return self._activation(\n",
    "            torch.add(torch.bmm(x, self.weight), self.bias[:, None, :]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "869942c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "class EnsembleModel(nn.Module):\n",
    "    ''' 环境模型集成 '''\n",
    "    def __init__(self,\n",
    "                 state_dim,\n",
    "                 action_dim,\n",
    "                 model_alpha,\n",
    "                 ensemble_size=5,\n",
    "                 learning_rate=1e-3):\n",
    "        super(EnsembleModel, self).__init__()\n",
    "        # 输出包括均值和方差,因此是状态与奖励维度之和的两倍\n",
    "        self._output_dim = (state_dim + 1) * 2\n",
    "        self._model_alpha = model_alpha  # 模型损失函数中加权时的权重\n",
    "        self._max_logvar = nn.Parameter((torch.ones(\n",
    "            (1, self._output_dim // 2)).float() / 2).to(device),\n",
    "                                        requires_grad=False)\n",
    "        self._min_logvar = nn.Parameter((-torch.ones(\n",
    "            (1, self._output_dim // 2)).float() * 10).to(device),\n",
    "                                        requires_grad=False)\n",
    "\n",
    "        self.layer1 = FCLayer(state_dim + action_dim, 200, ensemble_size,\n",
    "                              Swish())\n",
    "        self.layer2 = FCLayer(200, 200, ensemble_size, Swish())\n",
    "        self.layer3 = FCLayer(200, 200, ensemble_size, Swish())\n",
    "        self.layer4 = FCLayer(200, 200, ensemble_size, Swish())\n",
    "        self.layer5 = FCLayer(200, self._output_dim, ensemble_size,\n",
    "                              nn.Identity())\n",
    "        self.apply(init_weights)  # 初始化环境模型中的参数\n",
    "        self.optimizer = torch.optim.Adam(self.parameters(), lr=learning_rate)\n",
    "\n",
    "    def forward(self, x, return_log_var=False):\n",
    "        ret = self.layer5(self.layer4(self.layer3(self.layer2(\n",
    "            self.layer1(x)))))\n",
    "        mean = ret[:, :, :self._output_dim // 2]\n",
    "        # 在PETS算法中,将方差控制在最小值和最大值之间\n",
    "        logvar = self._max_logvar - F.softplus(\n",
    "            self._max_logvar - ret[:, :, self._output_dim // 2:])\n",
    "        logvar = self._min_logvar + F.softplus(logvar - self._min_logvar)\n",
    "        return mean, logvar if return_log_var else torch.exp(logvar)\n",
    "\n",
    "    def loss(self, mean, logvar, labels, use_var_loss=True):\n",
    "        inverse_var = torch.exp(-logvar)\n",
    "        if use_var_loss:\n",
    "            mse_loss = torch.mean(torch.mean(torch.pow(mean - labels, 2) *\n",
    "                                             inverse_var,\n",
    "                                             dim=-1),\n",
    "                                  dim=-1)\n",
    "            var_loss = torch.mean(torch.mean(logvar, dim=-1), dim=-1)\n",
    "            total_loss = torch.sum(mse_loss) + torch.sum(var_loss)\n",
    "        else:\n",
    "            mse_loss = torch.mean(torch.pow(mean - labels, 2), dim=(1, 2))\n",
    "            total_loss = torch.sum(mse_loss)\n",
    "        return total_loss, mse_loss\n",
    "\n",
    "    def train(self, loss):\n",
    "        self.optimizer.zero_grad()\n",
    "        loss += self._model_alpha * torch.sum(\n",
    "            self._max_logvar) - self._model_alpha * torch.sum(self._min_logvar)\n",
    "        loss.backward()\n",
    "        self.optimizer.step()\n",
    "\n",
    "\n",
    "class EnsembleDynamicsModel:\n",
    "    ''' 环境模型集成,加入精细化的训练 '''\n",
    "    def __init__(self, state_dim, action_dim, model_alpha=0.01, num_network=5):\n",
    "        self._num_network = num_network\n",
    "        self._state_dim, self._action_dim = state_dim, action_dim\n",
    "        self.model = EnsembleModel(state_dim,\n",
    "                                   action_dim,\n",
    "                                   model_alpha,\n",
    "                                   ensemble_size=num_network)\n",
    "        self._epoch_since_last_update = 0\n",
    "\n",
    "    def train(self,\n",
    "              inputs,\n",
    "              labels,\n",
    "              batch_size=64,\n",
    "              test_ratio=0.1,\n",
    "              max_iter=20):\n",
    "        # 设置训练集与验证集\n",
    "        permutation = np.random.permutation(inputs.shape[0])\n",
    "        inputs, labels = inputs[permutation], labels[permutation]\n",
    "        num_test = int(inputs.shape[0] * test_ratio)\n",
    "        train_inputs, train_labels = inputs[num_test:], labels[num_test:]\n",
    "        test_inputs, test_labels = inputs[:num_test], labels[:num_test]\n",
    "        test_inputs = torch.from_numpy(test_inputs).float().to(device)\n",
    "        test_labels = torch.from_numpy(test_labels).float().to(device)\n",
    "        test_inputs = test_inputs[None, :, :].repeat(\n",
    "            [self._num_network, 1, 1])\n",
    "        test_labels = test_labels[None, :, :].repeat(\n",
    "            [self._num_network, 1, 1])\n",
    "\n",
    "        # 保留最好的结果\n",
    "        self._snapshots = {i: (None, 1e10) for i in range(self._num_network)}\n",
    "\n",
    "        for epoch in itertools.count():\n",
    "            # 定义每一个网络的训练数据\n",
    "            train_index = np.vstack([\n",
    "                np.random.permutation(train_inputs.shape[0])\n",
    "                for _ in range(self._num_network)\n",
    "            ])\n",
    "            # 所有真实数据都用来训练\n",
    "            for batch_start_pos in range(0, train_inputs.shape[0], batch_size):\n",
    "                batch_index = train_index[:, batch_start_pos:batch_start_pos +\n",
    "                                          batch_size]\n",
    "                train_input = torch.from_numpy(\n",
    "                    train_inputs[batch_index]).float().to(device)\n",
    "                train_label = torch.from_numpy(\n",
    "                    train_labels[batch_index]).float().to(device)\n",
    "\n",
    "                mean, logvar = self.model(train_input, return_log_var=True)\n",
    "                loss, _ = self.model.loss(mean, logvar, train_label)\n",
    "                self.model.train(loss)\n",
    "\n",
    "            with torch.no_grad():\n",
    "                mean, logvar = self.model(test_inputs, return_log_var=True)\n",
    "                _, test_losses = self.model.loss(mean,\n",
    "                                                    logvar,\n",
    "                                                    test_labels,\n",
    "                                                    use_var_loss=False)\n",
    "                test_losses = test_losses.cpu()\n",
    "                break_condition = self._save_best(epoch, test_losses)\n",
    "                if break_condition or epoch > max_iter:  # 结束训练\n",
    "                    break\n",
    "\n",
    "    def _save_best(self, epoch, losses, threshold=0.1):\n",
    "        updated = False\n",
    "        for i in range(len(losses)):\n",
    "            current = losses[i]\n",
    "            _, best = self._snapshots[i]\n",
    "            improvement = (best - current) / best\n",
    "            if improvement > threshold:\n",
    "                self._snapshots[i] = (epoch, current)\n",
    "                updated = True\n",
    "        self._epoch_since_last_update = 0 if updated else self._epoch_since_last_update + 1\n",
    "        return self._epoch_since_last_update > 5\n",
    "\n",
    "    def predict(self, inputs, batch_size=64):\n",
    "        inputs = np.tile(inputs, (self._num_network, 1, 1))\n",
    "        inputs = torch.tensor(inputs, dtype=torch.float).to(device)\n",
    "        mean, var = self.model(inputs, return_log_var=False)\n",
    "        return mean.detach().cpu().numpy(), var.detach().cpu().numpy()\n",
    "\n",
    "\n",
    "class FakeEnv:\n",
    "    def __init__(self, model):\n",
    "        '''\n",
    "        类定义与作用\n",
    "        功能：将训练好的环境模型（如 EnsembleDynamicsModel）包装成一个模拟环境，用于替代真实环境进行策略训练。\n",
    "        核心思想：通过模型预测的状态转移和奖励，生成虚拟的环境交互数据，实现基于模型的强化学习（Model-Based RL），减少对真实环境的依赖，提升数据效率。\n",
    "        '''\n",
    "        self.model = model\n",
    "\n",
    "    def step(self, obs, act):\n",
    "        '''\n",
    "        step方法\n",
    "        输入参数\n",
    "            obs：当前观测状态（通常为 numpy 数组，形状为 (batch_size, state_dim)）。\n",
    "            act：智能体采取的动作（形状为 (batch_size, action_dim)）。\n",
    "        '''\n",
    "        inputs = np.concatenate((obs, act), axis=-1)\n",
    "        ''' \n",
    "        输入预处理：\n",
    "            将状态 obs 和动作 act 拼接为模型的输入特征，形状为 (batch_size, state_dim + action_dim)，\n",
    "            符合环境模型对输入格式的要求（如 EnsembleModel 的输入为状态 - 动作对）。\n",
    "        '''\n",
    "        ensemble_model_means, ensemble_model_vars = self.model.predict(inputs)\n",
    "        '''\n",
    "        模型预测：\n",
    "        调用 EnsembleDynamicsModel 的 predict 方法，输出集成模型的预测结果：\n",
    "            ensemble_model_means：形状为 (num_models, batch_size, output_dim)，其中 output_dim = (state_dim + 1) * 2\n",
    "            （对应状态转移和奖励的均值，见 EnsembleModel 的定义）。\n",
    "            ensemble_model_vars：预测结果的方差（形状同上）。\n",
    "        '''\n",
    "        ensemble_model_means[:, :, 1:] += obs\n",
    "        '''\n",
    "        处理状态转移的均值：\n",
    "            关键操作：环境模型通常预测的是状态变化量（Δstate）和奖励，而非绝对状态。因此，需要将当前状态 obs 与预测的状态变化量相加，得到下一时刻的绝对状态预测值。\n",
    "            假设 output_dim 的前 1 维为奖励预测，剩余维度为状态变化量，则 [:, :, 1:] 表示所有模型对状态变化量的预测，加上当前状态 obs 后得到绝对状态。\n",
    "        '''\n",
    "        ensemble_model_stds = np.sqrt(ensemble_model_vars)\n",
    "        # 计算标准差\n",
    "        ensemble_samples = ensemble_model_means + np.random.normal(\n",
    "            size=ensemble_model_means.shape) * ensemble_model_stds\n",
    "        '''\n",
    "        生成随机样本\n",
    "            基于预测的均值和方差，对每个模型的预测结果添加高斯噪声，模拟环境动态的不确定性。\n",
    "            噪声的标准差由模型预测的方差计算得到，体现了模型对自身预测的置信度（方差越小，噪声越小）\n",
    "        '''\n",
    "        num_models, batch_size, _ = ensemble_model_means.shape\n",
    "        models_to_use = np.random.choice(\n",
    "            [i for i in range(self.model._num_network)], size=batch_size)\n",
    "        batch_inds = np.arange(0, batch_size)\n",
    "        samples = ensemble_samples[models_to_use, batch_inds]\n",
    "        '''\n",
    "        集成模型采样:\n",
    "        集成模型的随机选择：从 num_models 个模型中为每个样本（batch 中的每个元素）随机选择一个模型，获取其预测样本。\n",
    "        作用：通过集成模型的随机性，增强模拟环境的多样性，避免单一模型的偏差，提升策略的鲁棒性（类似 “模型不确定性” 的建模）。\n",
    "        '''\n",
    "        rewards, next_obs = samples[:, :1][0][0], samples[:, 1:][0]\n",
    "        '''\n",
    "        解析输出：\n",
    "        从采样结果中提取奖励和下一状态：\n",
    "            samples[:, :1]：所有样本的奖励预测（形状为 (batch_size, 1)），通过 [0][0] 转换为标量（假设 batch_size=1，或取第一个元素）。\n",
    "            samples[:, 1:]：所有样本的下一状态预测（形状为 (batch_size, state_dim)）。\n",
    "        '''\n",
    "        return rewards, next_obs\n",
    "    ''' \n",
    "    关键技术点分析\n",
    "        状态转移的增量式建模：\n",
    "            环境模型预测的是状态变化量（而非绝对状态），这符合强化学习中动态模型的常见设计（如 Δstate = next_obs - obs），简化了模型输入输出的维度匹配。\n",
    "        不确定性的显式建模：\n",
    "            通过预测方差生成随机样本，体现了模型对预测结果的不确定性估计。高方差区域的样本会被赋予更大的噪声，促使智能体在策略优化时考虑不确定性（类似探索 - 利用平衡）。\n",
    "        集成模型的随机采样：\n",
    "            每个样本随机选择一个模型生成下一状态和奖励，相当于在模拟环境中引入了模型间的多样性，避免单一模型的过拟合问题，增强了模拟环境的真实性。\n",
    "        与真实环境的接口一致性：\n",
    "            step 方法的输入输出格式与真实环境（如 Gym 环境）一致，便于无缝替换真实环境进行训练，符合基于模型的 RL 算法（如 PETS）的流程设计。\n",
    "    '''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "a1636a88",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ReplayBuffer:\n",
    "    def __init__(self, capacity):\n",
    "        self.buffer = collections.deque(maxlen=capacity)\n",
    "\n",
    "    def add(self, state, action, reward, next_state, done):\n",
    "        self.buffer.append((state, action, reward, next_state, done))\n",
    "\n",
    "    def size(self):\n",
    "        return len(self.buffer)\n",
    "\n",
    "    def sample(self, batch_size):\n",
    "        if batch_size > len(self.buffer):\n",
    "            return self.return_all_samples()\n",
    "        else:\n",
    "            transitions = random.sample(self.buffer, batch_size)\n",
    "            state, action, reward, next_state, done = zip(*transitions)\n",
    "            return np.array(state), action, reward, np.array(next_state), done\n",
    "\n",
    "    def return_all_samples(self):\n",
    "        all_transitions = list(self.buffer)\n",
    "        state, action, reward, next_state, done = zip(*all_transitions)\n",
    "        return np.array(state), action, reward, np.array(next_state), done"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4f166f0",
   "metadata": {},
   "outputs": [],
   "source": [
    "class MBPO:\n",
    "    def __init__(self, env, agent, fake_env, env_pool, model_pool, rollout_length, rollout_batch_size, real_ratio, num_episode):\n",
    "        '''\n",
    "        类结构与初始化参数\n",
    "            env: 真实环境\n",
    "            agent: 智能体，负责决策和更新策略\n",
    "            fake_env: 环境模型，用于生成虚拟数据\n",
    "            env_pool: 真实数据池，存储从真实环境收集的数据\n",
    "            model_pool: 虚拟数据池，存储由环境模型生成的数据\n",
    "            rollout_length: 每次从环境模型采样的序列长度\n",
    "            rollout_batch_size: 每次从环境模型采样的批次大小\n",
    "            real_ratio: 更新策略时使用的真实数据比例\n",
    "            num_episode: 训练的总回合数\n",
    "        '''\n",
    "        self.env = env\n",
    "        self.agent = agent\n",
    "        self.fake_env = fake_env\n",
    "        self.env_pool = env_pool\n",
    "        self.model_pool = model_pool\n",
    "        self.rollout_length = rollout_length\n",
    "        self.rollout_bactch_size = rollout_batch_size\n",
    "        self.real_ratio = real_ratio\n",
    "        self.num_episode = num_episode\n",
    "\n",
    "    def rollout_model(self):\n",
    "        '''\n",
    "        rollout_model 方法\n",
    "        功能：使用环境模型生成虚拟数据\n",
    "        步骤：\n",
    "            从真实数据缓冲区采样初始观测\n",
    "            对每个初始观测，使用当前策略生成动作\n",
    "            通过环境模型预测下一个状态和奖励\n",
    "            将预测的转换存储到模型缓冲区\n",
    "            更新当前观测为预测的下一个观测  \n",
    "        '''\n",
    "        observations, _, _, _, _ = self.env_pool.sample(self.rollout_bactch_size)\n",
    "        for obs in observations:\n",
    "            for i in range(self.rollout_length):\n",
    "                action = self.agent.take_action(obs)\n",
    "                reward, next_obs = self.fake_env.step(obs, action)\n",
    "                self.model_pool.add(obs, action, reward, next_obs, False)\n",
    "                obs = next_obs\n",
    "\n",
    "    def update_agent(self, policy_train_batch_size=64):\n",
    "        '''\n",
    "        update_agent 方法\n",
    "        功能：更新策略网络\n",
    "        特点：\n",
    "            按比例混合真实数据和模型生成数据\n",
    "            如果虚拟数据池不为空，则按比例采样；否则仅使用真实数据\n",
    "            通过调整real_ratio平衡模型偏差和方差\n",
    "            将混合数据用于更新智能体策略\n",
    "            多次迭代更新提高样本效率\n",
    "        '''\n",
    "        env_batch_size = int(policy_train_batch_size * self.real_ratio)\n",
    "        model_batch_size = policy_train_batch_size - env_batch_size\n",
    "        for epoch in range(10):\n",
    "            env_obs, env_action, env_reward, env_next_obs, env_done = self.env_pool.sample(env_batch_size)\n",
    "            if self.model_pool.size() > 0:\n",
    "                model_obs, model_action, model_reward, model_next_obs, model_done = self.model_pool.sample(model_batch_size)\n",
    "                '''\n",
    "                env_obs和model_obs的两个数组沿着第 0 轴连接起来形成一个新的数组。\n",
    "                例如，若env_obs是 [[1, 2], [3, 4]]，model_obs是 [[5, 6], [7, 8]]，\n",
    "                拼接后会得到 [[1, 2], [3, 4], [5, 6], [7, 8]] 。\n",
    "                '''\n",
    "                obs = np.concatenate((env_obs, model_obs), axis=0)\n",
    "                action = np.concatenate((env_action, model_action), axis=0)\n",
    "                next_obs = np.concatenate((env_next_obs, model_next_obs), axis=0)\n",
    "                reward = np.concatenate((env_reward, model_reward), axis=0)\n",
    "                done = np.concatenate((env_done, model_done), axis=0)\n",
    "            else:\n",
    "                obs, action, reward, next_obs, done = env_obs, env_action, env_reward, env_next_obs, env_done\n",
    "            transition_dict = {'states': obs, 'actions': action, 'next_states': next_obs, 'rewards':reward, 'dones':done}\n",
    "            self.agent.update(transition_dict)\n",
    "    \n",
    "    def train_model(self):\n",
    "        '''\n",
    "        train_model 方法\n",
    "        功能：训练环境模型fake_env\n",
    "        输入 / 输出设计：\n",
    "            输入：状态和动作的拼接\n",
    "            输出：奖励和状态转移差的拼接\n",
    "            这种设计使模型学习状态变化而不是绝对状态，提高稳定性\n",
    "        '''\n",
    "        obs, action, reward, next_obs, done = self.env_pool.return_all_samples()\n",
    "        inputs = np.concatenate((obs, action), axis=1)\n",
    "        reward = np.array(reward)\n",
    "        labels = np.concatenate(\n",
    "            (np.reshape(reward, (reward.shape[0], -1)), next_obs - obs), axis=-1\n",
    "        )\n",
    "        self.fake_env.model.train(inputs, labels)\n",
    "    \n",
    "    def explore(self):\n",
    "        '''\n",
    "        explore 方法\n",
    "        功能：在真实环境中收集数据\n",
    "        特点：\n",
    "            使用当前策略进行探索\n",
    "            将交互数据存入真实数据缓冲区\n",
    "            返回当前回合的累积奖励\n",
    "        '''\n",
    "        obs, _ = self.env.reset()\n",
    "        done, episode_return = False, 0\n",
    "        while not done:\n",
    "            action = self.agent.take_action(obs)\n",
    "            next_obs, reward, terminated, truncated, info = self.env.step(action)\n",
    "            done = terminated or truncated\n",
    "            self.env_pool.add(obs, action, reward, next_obs, done)\n",
    "            obs = next_obs\n",
    "            episode_return += reward\n",
    "        return episode_return\n",
    "\n",
    "    def train(self):\n",
    "        '''\n",
    "        train 方法（主训练循环）\n",
    "        训练流程：\n",
    "            先进行一次探索收集初始数据\n",
    "        交替进行：\n",
    "            环境模型训练,每 50 步更新一次环境模型并生成新的虚拟数据\n",
    "            虚拟数据生成,与真实环境交互并存储数据\n",
    "            策略更新.每步都更新智能体策略\n",
    "            记录并打印每回合的回报\n",
    "        '''\n",
    "        return_list = []\n",
    "        explore_return = self.explore() # 随机探索采取数据\n",
    "        print('episode: 1, return: %d' % explore_return)\n",
    "        return_list.append(explore_return)\n",
    "\n",
    "        for i_episode in range(self.num_episode - 1):\n",
    "            obs, _ = self.env.reset()\n",
    "            done, episode_return = False, 0\n",
    "            step = 0\n",
    "            while not done:\n",
    "                if step % 50 == 0:\n",
    "                    self.train_model() # 训练环境模型，从env_pool: 真实数据池，存储从真实环境收集的数据拿出数据训练环境模型fake_env\n",
    "                    self.rollout_model() # 使用环境模型fake_env生成虚拟数据,添加到model_pool虚拟数据池，存储由环境模型生成的数据\n",
    "                action = self.agent.take_action(obs) \n",
    "                next_obs, reward, terminated, truncated, info = self.env.step(action)\n",
    "                done = terminated or truncated\n",
    "                self.env_pool.add(obs, action, reward, next_obs, done)\n",
    "                obs = next_obs\n",
    "                episode_return += reward\n",
    "\n",
    "                self.update_agent()\n",
    "                step += 1\n",
    "            return_list.append(episode_return)\n",
    "            print('episode: %d, return: %d' % (i_episode + 2, episode_return))\n",
    "        return return_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "d5aad5ee",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "episode: 1, return: -1194\n",
      "episode: 2, return: -1403\n",
      "episode: 3, return: -1545\n",
      "episode: 4, return: -1177\n",
      "episode: 5, return: -882\n",
      "episode: 6, return: -121\n",
      "episode: 7, return: -497\n",
      "episode: 8, return: -4\n",
      "episode: 9, return: -123\n",
      "episode: 10, return: -129\n",
      "episode: 11, return: -122\n",
      "episode: 12, return: -132\n",
      "episode: 13, return: -131\n",
      "episode: 14, return: -129\n",
      "episode: 15, return: -355\n",
      "episode: 16, return: -495\n",
      "episode: 17, return: -365\n",
      "episode: 18, return: -466\n",
      "episode: 19, return: -129\n",
      "episode: 20, return: -343\n"
     ]
    },
    {
     "data": {
      "image/png": "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Li7GXXfdZXTdsrIy9OnTp8EYiMg6MIEiIpvn4uKCjh07muVco0ePRnp6OrZt24b4+HiMGDEC06dPxwcffGCW8+vnS23duhXt2rUz2qef+G7pGIio+TgHiojs3sGDB2u879q1a53tfX19ERcXh2+++QYffvghvvjiCwBA165dcezYMRQVFRna/vHHH5DJZIiIiICHhwcCAwPx559/GvZXVFTg8OHDhvfdunWDUqlERkYGOnbsaPQKDg5uMAYisg7sgSIim6fRaJCZmWm0zcHBwTDxeuPGjejXrx/uvPNOfPvttzh06BD+7//+r9ZzLVy4EFFRUejevTs0Gg22bNliSLYeffRRLFq0CHFxcXjjjTeQk5ODF154AY8//jj8/f0BADNnzsS///1vdOrUCV26dMGyZcuQl5dnOL+bmxteeuklzJ49GzqdDnfeeSfy8/Pxxx9/wN3dHXFxcfXGQETWgQkUEdm87du3IzAw0GhbREQEUlJSAABvvvkmfvjhBzz//PMIDAzE999/j27dutV6LoVCgQULFuDChQtwcnLCoEGD8MMPPwAAnJ2dsWPHDsycORO33XYbnJ2d8eCDD2LZsmWG4+fOnYurV68iLi4OMpkMTz31FO6//37k5+cb2rz99tvw9fXFkiVLcP78eXh6eqJv377417/+1WAMRGQdBFG8pUAJEZEdEQQBmzdv5lIqRGRWnANFREREZCImUEREREQm4hwoIrJrnKVARJbAHigiIiIiEzGBIiIiIjIREygiIiIiEzGBIiIiIjIREygiIiIiEzGBIiIiIjIREygiIiIiEzGBIiIiIjIREygiIiIiE/1/virD+KskHjAAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "real_ratio = 0.5\n",
    "env_name = 'Pendulum-v1'\n",
    "env = gym.make(env_name)\n",
    "num_episodes = 20\n",
    "actor_lr = 5e-4\n",
    "critic_lr = 5e-3\n",
    "alpha_lr = 1e-3\n",
    "hidden_dim = 128\n",
    "gamma = 0.98\n",
    "tau = 0.005 # 软更新参数\n",
    "buffer_size = 10000\n",
    "target_entropy = -1\n",
    "model_alpha = 0.01 # 模型损失函数中的加权权重\n",
    "state_dim = env.observation_space.shape[0]\n",
    "action_dim = env.action_space.shape[0]\n",
    "action_bound = env.action_space.high[0] # 动作最大值\n",
    "\n",
    "rollout_batch_size = 1000\n",
    "rollout_length = 1 # 推演长度k,推荐更多尝试\n",
    "model_pool_size = rollout_batch_size * rollout_length\n",
    "\n",
    "agent = SAC(state_dim, hidden_dim, action_dim, action_bound, actor_lr, critic_lr, alpha_lr, target_entropy, tau, gamma)\n",
    "model = EnsembleDynamicsModel(state_dim, action_dim, model_alpha)\n",
    "fake_env = FakeEnv(model)\n",
    "env_pool = ReplayBuffer(buffer_size)\n",
    "model_pool = ReplayBuffer(model_pool_size)\n",
    "mbpo = MBPO(env, agent, fake_env, env_pool, model_pool, rollout_length, rollout_batch_size, real_ratio, num_episodes)\n",
    "\n",
    "return_list = mbpo.train()\n",
    "\n",
    "episodes_list = list(range(len(return_list)))\n",
    "plt.plot(episodes_list, return_list)\n",
    "plt.xlabel('Episodes')\n",
    "plt.ylabel('Returns')\n",
    "plt.title('MBPO on {}'.format(env_name))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "5dfe30c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys \n",
    "sys.path.append(\"..\") \n",
    "import rl_utils\n",
    "\n",
    "rl_utils.play_game(env_name, agent)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99015d05",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Iteration 0:   0%|          | 0/10 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Iteration 0: 100%|██████████| 10/10 [00:06<00:00,  1.43it/s, episode=10, return=-1310.281]\n",
      "Iteration 1: 100%|██████████| 10/10 [00:15<00:00,  1.53s/it, episode=20, return=-1160.913]\n",
      "Iteration 2: 100%|██████████| 10/10 [00:17<00:00,  1.70s/it, episode=30, return=-571.992]\n",
      "Iteration 3: 100%|██████████| 10/10 [00:13<00:00,  1.31s/it, episode=40, return=-176.413]\n",
      "Iteration 4: 100%|██████████| 10/10 [00:13<00:00,  1.34s/it, episode=50, return=-208.521]\n",
      "Iteration 5: 100%|██████████| 10/10 [00:13<00:00,  1.32s/it, episode=60, return=-171.376]\n",
      "Iteration 6: 100%|██████████| 10/10 [00:13<00:00,  1.32s/it, episode=70, return=-142.295]\n",
      "Iteration 7: 100%|██████████| 10/10 [00:13<00:00,  1.36s/it, episode=80, return=-183.728]\n",
      "Iteration 8: 100%|██████████| 10/10 [00:13<00:00,  1.33s/it, episode=90, return=-77.606]\n",
      "Iteration 9: 100%|██████████| 10/10 [00:13<00:00,  1.34s/it, episode=100, return=-144.138]\n"
     ]
    }
   ],
   "source": [
    "replay_buffer = ReplayBuffer(buffer_size)\n",
    "num_episodes = 100\n",
    "minimal_size = 1000\n",
    "batch_size = 64\n",
    "\n",
    "sac_agent = SAC(state_dim, hidden_dim, action_dim, action_bound, actor_lr, critic_lr, alpha_lr, target_entropy, tau, gamma)\n",
    "return_list = rl_utils.train_off_policy_agent(env, sac_agent, num_episodes, replay_buffer, minimal_size, batch_size)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "4e4af410",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlAAAAHHCAYAAABwaWYjAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAaahJREFUeJzt3Xd4U9X/B/D3TdqkM2mB7pbSUvaGQil7VApWEQUEf4hsBcGvDBUQBVERRUEREURlKChDxcGubKTsWaDMQgvddKR7JPf3R2kwtkBTkmb0/XqePJp7T3I/94Lt23POPVcQRVEEEREREVWaxNQFEBEREVkaBigiIiIiPTFAEREREemJAYqIiIhITwxQRERERHpigCIiIiLSEwMUERERkZ4YoIiIiIj0xABFREREpCcGKCKiatajRw/06NGjSp8dOXIk6tWrZ9B6iEh/DFBENcD58+cxaNAg+Pv7w87ODj4+PnjiiSewZMmSB37m+eefhyAImD59+kO/+8yZM3jxxRfh5+cHuVyOWrVqISwsDKtWrYJarTb0qVRJvXr1IAiC9uXu7o6uXbti8+bNpi6tRtmwYQNefPFFNGjQAIIgVDlEEpkDBigiK3f48GEEBwfj7NmzGDduHL766iuMHTsWEokEixcvrvAzKpUKf/31F+rVq4eff/4ZD3pk5nfffYfg4GDs3bsXw4YNw9dff43Zs2fD3t4eY8aMwSeffGLMU9NL69at8eOPP+LHH3/EG2+8gYSEBDz33HNYvny5qUurMZYtW4Y//vgDfn5+cHV1NXU5RI/FxtQFEJFxzZs3D0qlEsePH4eLi4vOvpSUlAo/8+uvv0KtVmPlypXo1asXDhw4gO7du+u0OXLkCMaPH4/Q0FBs27YNzs7O2n2TJ0/GiRMnEB0dbfDzqSofHx+8+OKL2vcvvfQSgoKC8Pnnn2P8+PEmrKzm+PHHH+Hj4wOJRILmzZubuhyix8IeKCIrd/36dTRr1qxceAIAd3f3Cj+zbt06PPHEE+jZsyeaNGmCdevWlWszd+5cCIKAdevW6YSnMsHBwRg5cuQj6/v666/RrFkzyOVyeHt7Y+LEicjMzNRp06NHDzRv3hwXL15Ez5494eDgAB8fHyxYsOCR3/8gnp6eaNKkCWJjY7Xb7ty5g9GjR8PDwwNyuRzNmjXDypUrdT63b98+CIKAjRs3Yt68efD19YWdnR169+6Na9eulTvOihUrUL9+fdjb26NDhw44ePBguTarV6+GIAi4efNmhcfat2/fA8/jQW1u3rwJQRCwevVq7baRI0fCyckJcXFxeOqpp+Dk5AQfHx8sXboUQOlQb69eveDo6Ah/f3/89NNPDzxumebNm6Nnz57ltms0Gvj4+GDQoEHabX5+fpBI+GuHrAP/JhNZOX9/f5w8ebLSvUEJCQnYu3cvXnjhBQDACy+8gF9++QVFRUXaNnl5edi9eze6deuGunXrVrm29957DxMnToS3tzcWLlyIgQMH4ptvvkGfPn1QXFys0zYjIwN9+/ZFq1atsHDhQjRu3BjTp0/H9u3bq3Ts4uJixMfHo3bt2gCA5ORkdOzYEX///TcmTZqExYsXIygoCGPGjMEXX3xR7vMff/wxNm/ejDfeeAMzZ87EkSNHMGzYMJ0233//PV555RV4enpiwYIF6Ny5M/r374/4+Pgq1WwIarUa/fr1g5+fHxYsWIB69eph0qRJWL16Nfr27Yvg4GB88skncHZ2xksvvaQTMCsyZMgQHDhwAElJSTrbDx06hISEBAwdOtSYp0NkOiIRWbVdu3aJUqlUlEqlYmhoqPjWW2+JO3fuFIuKiips/9lnn4n29vaiSqUSRVEUr1y5IgIQN2/erG1z9uxZEYD4+uuvV7mulJQUUSaTiX369BHVarV2+1dffSUCEFeuXKnd1r17dxGA+MMPP2i3FRYWip6enuLAgQMfeSx/f3+xT58+YmpqqpiamiqePXtWHDp0qAhAfO2110RRFMUxY8aIXl5eYlpams5nhw4dKiqVSjEvL08URVHcu3evCEBs0qSJWFhYqG23ePFiEYB4/vx5URRFsaioSHR3dxdbt26t027FihUiALF79+7abatWrRIBiLGxsTrHLjvW3r17tdtGjBgh+vv7P7SNKIpibGysCEBctWqVzmcBiB999JF2W0ZGhmhvby8KgiCuX79euz0mJkYEIM6ZM+eB11UURfHy5csiAHHJkiU621999VXRyclJe93+q1mzZjrXgMjSsAeKyMo98cQTiIqKQv/+/XH27FksWLAA4eHh8PHxwZ9//lmu/bp16xAREaEdlmvQoAHatWunM4ynUqkAoMKhu8r6+++/UVRUhMmTJ+sM64wbNw4KhQJbt27Vae/k5KQzh0kmk6FDhw64ceNGpY63a9cuuLm5wc3NDa1atcKmTZswfPhwfPLJJxBFEb/++iuefvppiKKItLQ07Ss8PBxZWVk4deqUzveNGjUKMplM+75r164AoK3nxIkTSElJwfjx43XajRw5EkqlspJXyTjGjh2r/XcXFxc0atQIjo6OeP7557XbGzVqBBcXl0de34YNG6J169bYsGGDdptarcYvv/yCp59+Gvb29oY/ASIzwEnkRDVA+/bt8dtvv6GoqAhnz57F5s2b8fnnn2PQoEE4c+YMmjZtCgC4dOkSTp8+jZdeeklnPk+PHj2wdOlSqFQqKBQKKBQKAEB2dnaVa7p16xaA0l/U/yaTyRAYGKjdX8bX1xeCIOhsc3V1xblz5yp1vJCQEHz44YcQBAEODg5o0qSJdl5YSkoKMjMzsWLFCqxYsaLCz/93wv1/hy7L7irLyMjQOb8GDRrotLO1tUVgYGClajYGOzs7uLm56WxTKpUVXl+lUqk9H7VajdTUVJ39tWrVgkwmw5AhQ/D222/jzp078PHxwb59+5CSkoIhQ4YY92SITIgBiqgGkclkaN++Pdq3b4+GDRti1KhR2LRpE+bMmQMAWLt2LQBgypQpmDJlSrnP//rrrxg1ahSCgoJgY2OD8+fPV1vtUqm0wu3iA5ZY+K86deogLCyswn0ajQYA8OKLL2LEiBEVtmnZsqVB6/m3/waXMpVZR0vfzz6o7kedT3x8PAICAnT27d27Fz169MCQIUMwc+ZMbNq0CZMnT8bGjRuhVCrRt2/fR9ZPZKkYoIhqqODgYABAYmIigNJflD/99BN69uyJV199tVz7Dz74AOvWrcOoUaPg4OCAXr16Yc+ePYiPj4efn5/ex/f39wcAXL58WadHpqioCLGxsQ8MO8bg5uYGZ2dnqNVqgx237PyuXr2KXr16abcXFxcjNjYWrVq10m4r6736792H/+2Fq8jjfFYfnp6eiIyM1NlWdg4BAQHo0KEDNmzYgEmTJuG3337DgAEDIJfLDVoDkTnhHCgiK7d3794Ke0W2bdsG4P4Q2j///IObN29i1KhRGDRoULnXkCFDsHfvXiQkJAAA5syZA1EUMXz4cOTk5JT7/pMnT2LNmjUPrCssLAwymQxffvmlTn3ff/89srKyEBER8VjnrQ+pVIqBAwfi119/rfBuxf8OXVVGcHAw3NzcsHz5cp07GFevXl0u7NSvXx8AcODAAe02tVr9wOHEf/P394dUKtX5LFC6PIQh2dnZISwsTOf178UwhwwZgiNHjmDlypVIS0vj8B1ZPfZAEVm51157DXl5eXj22WfRuHFjFBUV4fDhw9iwYQPq1auHUaNGASidPC6VSh8YXPr3749Zs2Zh/fr1mDp1Kjp16oSlS5fi1VdfRePGjTF8+HA0aNAA2dnZ2LdvH/788098+OGHD6zLzc0NM2fOxNy5c9G3b1/0798fly9fxtdff4327dvrTBivDh9//DH27t2LkJAQjBs3Dk2bNkV6ejpOnTqFv//+G+np6Xp9n62tLT788EO88sor6NWrF4YMGYLY2FisWrWq3ByoZs2aoWPHjpg5cybS09NRq1YtrF+/HiUlJY88jlKpxODBg7FkyRIIgoD69etjy5YtD1wk1Vief/55vPHGG3jjjTe0j/P5rwMHDmiDXmpqKnJzc7V/R7p164Zu3bpVa81Ej8Vk9/8RUbXYvn27OHr0aLFx48aik5OTKJPJxKCgIPG1114Tk5OTRVEsveW+du3aYteuXR/6XQEBAWKbNm10tp08eVL8v//7P9Hb21u0tbUVXV1dxd69e4tr1qzRWZ7gQb766iuxcePGoq2trejh4SFOmDBBzMjI0GnTvXt3sVmzZuU++99b+h/E399fjIiIeGS75ORkceLEiaKfn59oa2srenp6ir179xZXrFihbVO2bMCmTZt0PlvRsgGiKIpff/21GBAQIMrlcjE4OFg8cOCA2L1793K38F+/fl0MCwsT5XK56OHhIb799ttiZGTkI5cxEEVRTE1NFQcOHCg6ODiIrq6u4iuvvCJGR0dXuIyBo6NjufN+0PWt7HUr07lzZxGAOHbs2Ar3z5kzRwRQ4etRyyUQmRtBFKsw45GIiIioBuMcKCIiIiI9MUARERER6YkBioiIiEhPDFBEREREemKAIiIiItITAxQRERGRnriQphFoNBokJCTA2dn5gc+pIiIiIvMiiiKys7Ph7e0NieThfUwMUEaQkJBQpWeDERERkenFx8fD19f3oW0YoIzA2dkZQOkfgEKhMHE1REREVBkqlQp+fn7a3+MPwwBlBGXDdgqFggGKiIjIwlRm+g0nkRMRERHpiQGKiIiISE8MUERERER6YoAiIiIi0hMDFBEREZGeGKCIiIiI9MQARURERKQnBigiIiIiPTFA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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "episodes_list = list(range(len(return_list)))\n",
    "mv_return = rl_utils.moving_average(return_list, 9)\n",
    "plt.plot(episodes_list, mv_return)\n",
    "plt.xlabel('Episodes')\n",
    "plt.ylabel('Returns')\n",
    "plt.title(f'SAC on {env_name}')\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
